package hzmg.akkacloud.server.core.handler.impl;

import cn.hutool.core.convert.Convert;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives;
import com.hzmg.akkacloud.common.AkkaConstant;
import com.hzmg.akkacloud.common.ProtocolConstant;
import com.hzmg.akkacloud.common.exception.AkkaCloudException;
import com.hzmg.akkacloud.common.request.WorkerHeartbeat;
import com.hzmg.akkacloud.common.response.ResultDTO;
import hzmg.akkacloud.server.common.utils.PropertyUtils;
import hzmg.akkacloud.server.common.utils.SpringUtils;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;

import static hzmg.akkacloud.server.core.handler.WorkerRequestHandler.getWorkerRequestHandler;

/**
 * WorkerRequestHandler
 *
 * @author zbw
 * @since 2022/7/17
 */
@Slf4j
public class WorkerRequestHttpHandler extends AbstractVerticle {
    private Router router;

    private static void out(RoutingContext ctx, Object msg) {
        ctx.response()
                .putHeader(AkkaConstant.HTTP_HEADER_CONTENT_TYPE, AkkaConstant.JSON_MEDIA_TYPE)
                .end(JsonObject.mapFrom(msg).encode());
    }

    private static void success(RoutingContext ctx) {
        out(ctx, ResultDTO.success(null));
    }

    @Override
    public void start() {

        Properties properties = PropertyUtils.getProperties();
        int port = Integer.parseInt(properties.getProperty("server.port", String.valueOf(AkkaConstant.SERVER_DEFAULT_HTTP_PORT)));

        HttpServerOptions options = new HttpServerOptions();
        HttpServer server = vertx.createHttpServer(options);

        router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        Set<String> allowedHeaders = new HashSet<>();
        allowedHeaders.add("x-requested-with");
        allowedHeaders.add("Access-Control-Allow-Origin");
        allowedHeaders.add("origin");
        allowedHeaders.add("Content-Type");
        allowedHeaders.add("accept");
        allowedHeaders.add("X-PINGARUNER");

        Set<HttpMethod> allowedMethods = new HashSet<>();
        allowedMethods.add(HttpMethod.GET);
        allowedMethods.add(HttpMethod.POST);
        allowedMethods.add(HttpMethod.OPTIONS);
        /*
         * these methods aren't necessary for this sample,
         * but you may need them for your projects
         */
        allowedMethods.add(HttpMethod.DELETE);
        allowedMethods.add(HttpMethod.PATCH);
        allowedMethods.add(HttpMethod.PUT);

        router.route().handler(CorsHandler.create("*").allowedHeaders(allowedHeaders).allowedMethods(allowedMethods));
        router.post(ProtocolConstant.SERVER_PATH_HEARTBEAT)
                .handler(ctx -> {
                    WorkerHeartbeat heartbeat = ctx.getBodyAsJson().mapTo(WorkerHeartbeat.class);
                    getWorkerRequestHandler().onReceiveWorkerHeartbeat(heartbeat);
                    success(ctx);
                });
        //将所有的controller转换成router
        try {
            // 获取所有beanNames
            String[] beanNames = SpringUtils.getBeanNamesForType(Object.class);
            for (String beanName : beanNames) {
                RestController restController = SpringUtils.findAnnotationOnBean(beanName,
                        RestController.class);
                //判断该类是否含有RestController注解
                if (restController != null) {
                    RequestMapping requestMapping = SpringUtils.findAnnotationOnBean(beanName,
                            RequestMapping.class);
                    String prefixName;
                    if (requestMapping == null) {
                        prefixName = "";
                    } else {
                        prefixName = "".equals(requestMapping.name()) ? requestMapping.value()[0] : requestMapping.name();
                    }
                    log.info("[vertx web] prefix:" + prefixName);
                    Object newInstance = SpringUtils.getBean(beanName);
                    Method[] methods = ReflectionUtils.getAllDeclaredMethods(newInstance.getClass());
                    for (Method method : methods) {
                        setSingleRouter(method, prefixName, newInstance);
                    }
                }
            }
            log.info("[Vertx web] Create web for Spring-web with vertx web succeed!");
        } catch (Exception e) {
            throw new AkkaCloudException(e.toString());
        }
        server.requestHandler(router).listen(port);
    }

    private void setSingleRouter(Method method, String prefix, Object classBean) {
        if (method.getAnnotation(GetMapping.class) != null) {
            String url = "".equals(method.getAnnotation(GetMapping.class).name()) ? method.getAnnotation(GetMapping.class).value()[0] : method.getAnnotation(GetMapping.class).name();
            log.info("[vertx web] method api(get):" + prefix + url);
            try {
                router.get(prefix + url)
                        .handler(ctx -> webHandle(ctx, method, prefix, url, classBean));
            } catch (Exception e) {
                e.printStackTrace();
            }

        } else if (method.getAnnotation(PostMapping.class) != null) {
            String url = "".equals(method.getAnnotation(PostMapping.class).name()) ? method.getAnnotation(PostMapping.class).value()[0] : method.getAnnotation(PostMapping.class).name();
            log.info("[vertx web] method api(post):" + prefix + url);
            router.post(prefix + url)
                    .handler(ctx -> webHandle(ctx, method, prefix, url, classBean));
        }
    }

    private List<? extends Class<? extends Annotation>> getParameterAnnotations(Method method, int i) {
        return Arrays
                .stream(method.getParameterAnnotations()[i])
                .map(Annotation::annotationType)
                .collect(Collectors.toList());
    }

    private void webHandle(RoutingContext ctx, Method method, String prefix, String url, Object classBean) {
        //该方法所有的参数
        List<Object> parameters = Lists.newArrayList();
        //遍历该方法的所有参数注解（注：可能有不带注解的参数）
        for (int i = 0; i < method.getParameterCount(); i++) {
            List<? extends Class<? extends Annotation>> parameterAnnotations = getParameterAnnotations(method, i);
            if (parameterAnnotations.contains(RequestBody.class)) {
                Class<?> parameterClass = method.getParameterTypes()[i];
                parameters.add(ctx.getBodyAsJson().mapTo(parameterClass));
                log.info("[vertx web] get RequestBody parameter:{}", ctx.getBodyAsJson().mapTo(parameterClass));
            } else if (parameterAnnotations.contains(RequestParam.class)) {
                RequestParam requestParam = method.getParameters()[i].getAnnotation(RequestParam.class);
                String param;
                if ("\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n".equals(requestParam.defaultValue())) {
                    param = ctx.request().getParam(requestParam.value());
                    if (param == null && requestParam.required()) {
                        out(ctx, ResultDTO.failed("this parameter could not be null,maybe you should add a annotation:request=false"));
                        return;
                    }
                } else {

                    param = "".equals(ctx.request().getParam(requestParam.value())) ? requestParam.defaultValue() : ctx.request().getParam(requestParam.value());
                }
                Object paramResult = typeConverter(param, method.getParameters()[i]);
                log.info("[vertx web] parameterType name is {},get RequestParam parameter:{},final result is:{}", method.getParameters()[i].getType().getTypeName(), param, paramResult);
                parameters.add(paramResult);
            } else {
                log.warn("[vertx web] no annotation in this parameter,please add annotation :RequestParam");
                return;
            }
        }
        Object[] objects = new Object[parameters.size()];
        for (int i = 0; i < parameters.size(); i++) {
            objects[i] = parameters.get(i);
            log.info("[vertx web] object:{}", objects[i]);
        }
        log.info("[vertx web] objects:{}", Arrays.toString(objects));
        log.info("[vertx web] api has benn invoke.The api name is:" + prefix + url);
        if ("void".equals(method.getReturnType().getTypeName())) {
            log.info("[vertx web] this method returnType is void");
            try {
                //method.invoke(classBean,parameters);
                ReflectionUtils.invokeMethod(method, classBean, objects);
            } catch (Exception e) {
                throw new AkkaCloudException(e.toString());
            }
            out(ctx, ResultDTO.success("ok"));
        } else {
            log.info("[vertx web] this method returnType isn't void");
            try {
                out(ctx, ReflectionUtils.invokeMethod(method, classBean, objects));

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private Object typeConverter(String obj, Parameter parameter) {
        try {
            return Convert.convertQuietly(parameter.getType(), obj, null);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return obj;
    }

}
